Skip to content

Fixes to reflection cache#381

Open
tunabrain wants to merge 5 commits into
mainfrom
dev/bbitterli/reflection-cache
Open

Fixes to reflection cache#381
tunabrain wants to merge 5 commits into
mainfrom
dev/bbitterli/reflection-cache

Conversation

@tunabrain
Copy link
Copy Markdown
Collaborator

@tunabrain tunabrain commented Jul 28, 2025

This is a (very simple!) fix to a reflection caching bug that causes a range of convoluted bugs and weird behavior.

Anytime we look up a function, whether by name or by reflection, we construct a new cache key representing the function name and save the reflection for future lookups and hot reloads.

This causes a few problems:

  1. The cache key we build can be different from the name used to look up the function, e.g. for specialized generics. This makes SlangPy act differently depending on your history of function calls. For the function int foo<int X>() { return X; }, we can look up the specialized function "foo<5>". SlangPy checks the cache for prior lookups of "foo<5>", but saves it in the cache under the name "foo". This means lookups for specialized generics never hit the cache.
    Even weirder: Calling foo() directly is not allowed, since X can't be inferred. But if you called or looked up "foo<5>" at some earlier point, calling foo() now suddenly succeeds, because the cache returns the function foo<5> instead when you look up "foo".
  2. We sometimes cache reflection objects by name that can't actually be looked up by name: E.g. for an overloaded function, it's specializations can't be retrieved by name and they shouldn't be cached. For void foo(int x) {} void foo(int2 x) {}, reflecting "foo" returns an overloaded reflection object. As a result of how the reflection API handles these, SlangPy instead caches this lookup under None. OTOH, when slangpy specializes the function to get a specific overload (e.g. foo(int)), this one is cached under "foo". From then on, looking up the function "foo" by name returns that specific specialization, not the overloaded function, and trying to call e.g. foo(int2) after that could fail. It's surprising this hasn't caused issues before - our savior here is that the Module keeps a separate _attr_cache and avoids hitting the (incorrect) reflection cache most of the time.
  3. As a result of 2), if you ever reflected an overloaded function, hot reload crashes, as it will try to re-lookup a None function name.

The fix is very simple, and moves the by-name-caching into the functions that look up by name, and caches under the exact name used to query the reflection API.

Summary by CodeRabbit

  • Refactor

    • Improved internal function name caching logic for better organization and clarity.
  • Tests

    • Added validation tests for overloaded functions and generic specialization to ensure correct behavior.

@tunabrain tunabrain requested a review from a team as a code owner July 28, 2025 22:07
ccummingsNV
ccummingsNV previously approved these changes Jul 29, 2025
Copy link
Copy Markdown
Contributor

@ccummingsNV ccummingsNV left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one does make me a bit nervous, as I went through a lot of combinations of working out how to cache these things. If you're confident it's the right fix though, go for it.

@tunabrain
Copy link
Copy Markdown
Collaborator Author

If there are sanity checks you would want to run, or more extensive tests you can think of that would make you feel less nervous, I'm happy to add them.

@jhelferty-nv
Copy link
Copy Markdown
Contributor

@tunabrain Is this still being worked on?

@jkwak-work
Copy link
Copy Markdown
Contributor

@tunabrain , can you resolve the merge conflict?

@tunabrain
Copy link
Copy Markdown
Collaborator Author

Merge conflict resolved.

@jkwak-work
Copy link
Copy Markdown
Contributor

@ccummingsNV , can you review the change?

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

📝 Walkthrough

Walkthrough

SlangProgramLayout's function-name caching strategy was restructured to store resolved SlangFunction instances separately by lookup path: simple name lookups populate _functions_by_name[name], while type-qualified lookups populate _functions_by_name[qualified_name]. The shared _get_or_create_function method now only updates _functions_by_reflection, delegating cache population to callers. Tests validate that overloaded functions and generic specializations do not overwrite each other's cache entries.

Changes

Function Caching Refactor

Layer / File(s) Summary
Cache Population Logic
slangpy/reflection/reflectiontypes.py
find_function_by_name and find_function_by_name_in_type now directly populate _functions_by_name with resolved SlangFunction objects; _get_or_create_function delegated cache updates to only _functions_by_reflection.
Overload and Specialization Tests
slangpy/tests/slangpy_tests/test_reflection2.py
New test_overloads validates that overloaded function lookups preserve overload metadata across specialization resolution; test_generic_specialization extended to confirm unspecialized generics retain their original full_name.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 The caches now know their true names,
No overloads lost in the lookup flames,
Each type qualifies, each function stands clear—
Specialization's symphony, now crystal here! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fixes to reflection cache' directly corresponds to the main change, which involves fixing reflection caching bugs by moving by-name caching into specific lookup functions.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev/bbitterli/reflection-cache

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
slangpy/reflection/reflectiontypes.py (1)

1603-1614: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Reflection-only SlangFunctions stop participating in hot reload.

After this change, _get_or_create_function() only records wrappers in _functions_by_reflection, but on_hot_reload() rebuilds state exclusively from _functions_by_name (Lines 1424-1445). Any function created through find_function(...) or specialize_with_arg_types(...) now keeps a stale FunctionReflection across reload because there is no reload path for it anymore. The new overload test already creates one of these reflection-only entries on Line 110.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@slangpy/reflection/reflectiontypes.py` around lines 1603 - 1614, The bug:
functions created only via reflection are stored in _functions_by_reflection by
_get_or_create_function but on_hot_reload rebuilds state solely from
_functions_by_name, so reflection-only SlangFunction instances (created by
find_function or specialize_with_arg_types using FunctionReflection) are not
re-registered on reload and become stale; to fix, ensure reflection-created
functions are also registered into the name-based registry during creation (or
add a reload path that rebuilds from _functions_by_reflection). Concretely,
update _get_or_create_function to, after calling _reflect_function(refl, this,
full_name) and before returning, also insert the returned SlangFunction into
_functions_by_name (using its unique name key as used by on_hot_reload) or
extend on_hot_reload to iterate _functions_by_reflection entries and rebuild
those functions; reference: _get_or_create_function, _functions_by_reflection,
_functions_by_name, on_hot_reload, find_function, specialize_with_arg_types,
FunctionReflection, SlangFunction.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@slangpy/reflection/reflectiontypes.py`:
- Around line 1603-1614: The bug: functions created only via reflection are
stored in _functions_by_reflection by _get_or_create_function but on_hot_reload
rebuilds state solely from _functions_by_name, so reflection-only SlangFunction
instances (created by find_function or specialize_with_arg_types using
FunctionReflection) are not re-registered on reload and become stale; to fix,
ensure reflection-created functions are also registered into the name-based
registry during creation (or add a reload path that rebuilds from
_functions_by_reflection). Concretely, update _get_or_create_function to, after
calling _reflect_function(refl, this, full_name) and before returning, also
insert the returned SlangFunction into _functions_by_name (using its unique name
key as used by on_hot_reload) or extend on_hot_reload to iterate
_functions_by_reflection entries and rebuild those functions; reference:
_get_or_create_function, _functions_by_reflection, _functions_by_name,
on_hot_reload, find_function, specialize_with_arg_types, FunctionReflection,
SlangFunction.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a671a61f-d2f0-4a06-9645-921c3c91fc17

📥 Commits

Reviewing files that changed from the base of the PR and between e65840a and a6d7b47.

📒 Files selected for processing (2)
  • slangpy/reflection/reflectiontypes.py
  • slangpy/tests/slangpy_tests/test_reflection2.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants